{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9b92525f-45cd-4889-a90a-54806076ec3c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Setup OpenAI with credentials\n",
    "import openai\n",
    "\n",
    "openai.api_key = \"sk-your-key\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30c4131a-59a0-4989-af61-59e142c702ff",
   "metadata": {},
   "source": [
    "## Leveraging the GraphQL schema in our Agent\n",
    "\n",
    "The schema was retrieved using the `apollo client:download-schema` command: `apollo client:download-schema download3.json --endpoint=https://your-store.myshopify.com/admin/api/2023-01/graphql.json --header=\"X-Shopify-Access-Token: your-token\"`\n",
    "\n",
    "All in all, the file is over 50,000 lines and close to 1.5 million characters, well beyond what we could hope to process directly with any Large Language Model. Instead, we have to get creative with how we will process and retrieve it.\n",
    "\n",
    "In the below code block we open the GraphQL schema for the Shopify store and parse out the **QueryRoot** objects.\n",
    "These are then directly passed into the system prompt, so that the Agent is aware of the objects it can query against.\n",
    "From the schema, a **QueryRoot** is `The schema's entry-point for queries. This acts as the public, top-level API from which all queries must start.` Because these obejcts are so critical to writing good queries, it's worth passing them into the agent.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "792ac8b6-02aa-4858-8e65-7f78e6f73958",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['abandonment', 'abandonmentByAbandonedCheckoutId', 'app', 'appByHandle', 'appByKey', 'appDiscountType', 'appDiscountTypes', 'appInstallation', 'appInstallations', 'automaticDiscount', 'automaticDiscountNode', 'automaticDiscountNodes', 'automaticDiscountSavedSearches', 'automaticDiscounts', 'availableCarrierServices', 'availableLocales', 'carrierService', 'channel', 'channels', 'checkoutProfile', 'checkoutProfiles', 'codeDiscountNode', 'codeDiscountNodeByCode', 'codeDiscountNodes', 'codeDiscountSavedSearches', 'collection', 'collectionByHandle', 'collectionRulesConditions', 'collectionSavedSearches', 'collections', 'companies', 'company', 'companyContact', 'companyContactRole', 'companyCount', 'companyLocation', 'companyLocations', 'currentAppInstallation', 'currentBulkOperation', 'customer', 'customerPaymentMethod', 'customerSegmentMembers', 'customerSegmentMembersQuery', 'customerSegmentMembership', 'customers', 'deletionEvents', 'deliveryProfile', 'deliveryProfiles', 'deliverySettings', 'discountCodeCount', 'discountNode', 'discountNodes', 'discountRedeemCodeBulkCreation', 'discountRedeemCodeSavedSearches', 'dispute', 'disputeEvidence', 'domain', 'draftOrder', 'draftOrderSavedSearches', 'draftOrderTag', 'draftOrders', 'fileSavedSearches', 'files', 'fulfillment', 'fulfillmentOrder', 'fulfillmentOrders', 'fulfillmentService', 'giftCard', 'giftCards', 'giftCardsCount', 'inventoryItem', 'inventoryItems', 'inventoryLevel', 'inventoryProperties', 'job', 'location', 'locations', 'locationsAvailableForDeliveryProfiles', 'locationsAvailableForDeliveryProfilesConnection', 'manualHoldsFulfillmentOrders', 'market', 'marketByGeography', 'marketLocalizableResource', 'marketLocalizableResources', 'marketLocalizableResourcesByIds', 'marketingActivities', 'marketingActivity', 'marketingEvent', 'marketingEvents', 'markets', 'metafield', 'metafieldDefinition', 'metafieldDefinitionTypes', 'metafieldDefinitions', 'metafieldStorefrontVisibilities', 'metafieldStorefrontVisibility', 'metaobject', 'metaobjectByHandle', 'metaobjectDefinition', 'metaobjectDefinitionByType', 'metaobjectDefinitions', 'metaobjects', 'node', 'nodes', 'order', 'orderPaymentStatus', 'orderSavedSearches', 'orders', 'paymentTermsTemplates', 'priceList', 'priceLists', 'priceRule', 'priceRuleSavedSearches', 'priceRules', 'primaryMarket', 'privateMetafield', 'privateMetafields', 'product', 'productByHandle', 'productResourceFeedback', 'productSavedSearches', 'productVariant', 'productVariants', 'products', 'publicApiVersions', 'publication', 'publications', 'refund', 'return', 'returnableFulfillment', 'returnableFulfillments', 'reverseDelivery', 'reverseFulfillmentOrder', 'scriptTag', 'scriptTags', 'segment', 'segmentCount', 'segmentFilterSuggestions', 'segmentFilters', 'segmentMigrations', 'segmentValueSuggestions', 'segments', 'sellingPlanGroup', 'sellingPlanGroups', 'shop', 'shopLocales', 'shopifyPaymentsAccount', 'shopifyqlQuery', 'staffMember', 'standardMetafieldDefinitionTemplates', 'subscriptionBillingAttempt', 'subscriptionBillingCycle', 'subscriptionBillingCycles', 'subscriptionContract', 'subscriptionContracts', 'subscriptionDraft', 'tenderTransactions', 'translatableResource', 'translatableResources', 'translatableResourcesByIds', 'urlRedirect', 'urlRedirectImport', 'urlRedirectSavedSearches', 'urlRedirects', 'webPixel', 'webhookSubscription', 'webhookSubscriptions']\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "from graphql import parse\n",
    "\n",
    "with open(\"data/shopify_graphql.txt\", \"r\") as f:\n",
    "    txt = f.read()\n",
    "\n",
    "ast = parse(txt)\n",
    "\n",
    "query_root_node = next(\n",
    "    (\n",
    "        defn\n",
    "        for defn in ast.definitions\n",
    "        if defn.kind == \"object_type_definition\" and defn.name.value == \"QueryRoot\"\n",
    "    )\n",
    ")\n",
    "query_roots = [field.name.value for field in query_root_node.fields]\n",
    "print(query_roots)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6bab757-efc1-4c05-a369-99cb8034e1b9",
   "metadata": {},
   "source": [
    "## Setting up SDLReader and OnDemandLoaderTool\n",
    "\n",
    "We've successfully parsed out the **QueryRoot** fields that are usable for top level GraphQL queries. Now we can combine the **SDLReader** and **OnDemandLoaderTool** to create an interface that our Agent can use to query and process the GraphQL schema.\n",
    "\n",
    "The **SDLReader** is consuming our GraphQL spec and intelligently breaking it into chunks based on the definitions in the schema. By wrapping the **SDLReader** with the **OnDemandLoaderTool**, there is essentially a sub-model that is processing our query_str, retriving any relevant chunks of data from the GraphQL schema, and then intrpreting those chunks in relation to our query. This lets us ask arbitrary natural language questions, and get back intelligent responses based on the GraphQL schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a7c72b54-3d16-427f-ae6b-07fbba2e0d58",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "query {\n",
      "  shop {\n",
      "    products(first: 3) {\n",
      "      edges {\n",
      "        node {\n",
      "          name\n",
      "          description\n",
      "        }\n",
      "      }\n",
      "    }\n",
      "  }\n",
      "}\n",
      "\n",
      "The fields that can be retrieved from the products object are: descriptionHtml, handle, redirectNewHandle, seo, productType, standardizedProductType, productCategory, customProductType, tags, templateSuffix, giftCard, giftCardTemplateSuffix, title, vendor, collectionsToJoin, collectionsToLeave, id, metafields, options, variants, status, requiresSellingPlan, and productResourceFeedbackInput.\n"
     ]
    }
   ],
   "source": [
    "from llama_hub.file.sdl.base import SDLReader\n",
    "from llama_index.tools.ondemand_loader_tool import OnDemandLoaderTool\n",
    "\n",
    "documentation_tool = OnDemandLoaderTool.from_defaults(\n",
    "    SDLReader(),\n",
    "    name=\"graphql_writer\",\n",
    "    description=\"\"\"\n",
    "        The GraphQL schema file is located at './data/shopify_graphql.txt', this is always the file argument.\n",
    "        A tool for processing the Shopify GraphQL spec, and writing queries from the documentation.\n",
    "\n",
    "        You should pass a query_str to this tool in the form of a request to write a GraphQL query.\n",
    "\n",
    "        Examples:\n",
    "            file: './data/shopify_graphql.txt', query_str='Write a graphql query to find unshipped orders'\n",
    "            file: './data/shopify_graphql.txt', query_str='Write a graphql query to retrieve the stores products'\n",
    "            file: './data/shopify_graphql.txt', query_str='What fields can you retrieve from the orders object'\n",
    "\n",
    "        \"\"\",\n",
    ")\n",
    "\n",
    "print(\n",
    "    documentation_tool(\n",
    "        \"./data/shopify_graphql.txt\",\n",
    "        query_str=\"Write a graphql query to retrieve the first 3 products from a store\",\n",
    "    )\n",
    ")\n",
    "print(\n",
    "    documentation_tool(\n",
    "        \"./data/shopify_graphql.txt\",\n",
    "        query_str=\"what fields can you retrieve from the products object\",\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c513c6fa-6205-43a5-b140-539c21ec0a56",
   "metadata": {},
   "source": [
    "## Setting up the Shopify Tool\n",
    "\n",
    "We've now set up a tool that ourselves or an Agent can call with natural language, and get information or create queries based on our schema. We can now initialize the Shopify tool and even test it out with the prompt that was written, adding in some of the extra fields the documentation returned us:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "564a1e6d-0dc4-4bed-a6c5-38abf9a3f804",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"data\":{\"products\":{\"edges\":[{\"node\":{\"title\":\"The Minimal Snowboard\",\"vendor\":\"Quickstart (6b2c02b2)\",\"productType\":\"\",\"status\":\"ACTIVE\"}},{\"node\":{\"title\":\"The Videographer Snowboard\",\"vendor\":\"Quickstart (6b2c02b2)\",\"productType\":\"\",\"status\":\"ACTIVE\"}},{\"node\":{\"title\":\"The Draft Snowboard\",\"vendor\":\"Snowboard Vendor\",\"productType\":\"\",\"status\":\"DRAFT\"}}]}},\"extensions\":{\"cost\":{\"requestedQueryCost\":5,\"actualQueryCost\":5,\"throttleStatus\":{\"maximumAvailable\":1000.0,\"currentlyAvailable\":995,\"restoreRate\":50.0}}}}'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from llama_hub.tools.shopify.base import ShopifyToolSpec\n",
    "\n",
    "shopify_tool = ShopifyToolSpec(\"your-store.myshopify.com\", \"2023-04\", \"your-key\")\n",
    "\n",
    "shopify_tool.run_graphql_query(\n",
    "    \"\"\"\n",
    "query {\n",
    "  products(first: 3) {\n",
    "    edges {\n",
    "      node {\n",
    "        title\n",
    "        vendor\n",
    "        productType\n",
    "        status\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "}\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c1ab5d7-1919-4a5d-9e80-cd2615f43aee",
   "metadata": {},
   "source": [
    "## Creating a Data Agent\n",
    "\n",
    "So now we have two tools, one that can create working GraphQL queries and provide information from a GraphQL schema using natural language strings, and one that can execute the GraphQL queries and return the results.\n",
    "\n",
    "Our next step is to pass these tools to a Data Agent, and allow them access to use the tools and interpret the outputs for the user. We supply the Agent with a system prompt on initilization that gives them some extra info, like the **QueryRoot** objects we processed above, and some instructions on how to effectively use the tools:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ac4bc289-f124-49f2-bbd0-7e4c816c8924",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the Agent with access to our tools\n",
    "from llama_index.agent import OpenAIAgent\n",
    "\n",
    "agent = OpenAIAgent.from_tools(\n",
    "    [*shopify_tool.to_tool_list(), documentation_tool],\n",
    "    system_prompt=f\"\"\"\n",
    "    You are a specialized Agent with access to the Shopify Admin GraphQL API for this Users online store.\n",
    "    Your job is to chat with store owners and help them run GraphQL queries, interpreting the results for the user\n",
    "    \n",
    "    For you conveinence, the QueryRoot objects are listed here.\n",
    "    \n",
    "    {query_roots}\n",
    "    \n",
    "    QueryRoots are the schema's entry-point for queries. This acts as the public, top-level API from which all queries must start.\n",
    "    \n",
    "    You can use graphql_writer to query the schema and assist in writing queries.\n",
    "    \n",
    "    If the GraphQL you execute returns an error, either directly fix the query, or directly ask the graphql_writer questions about the schema instead of writing graphql queries.\n",
    "    Then use that information to write the correct graphql query\n",
    "    \"\"\",\n",
    "    verbose=True,\n",
    "    max_function_calls=20,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9d48fcdd-1802-4340-87f2-0e4bd653c495",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: graphql_writer with args: {\n",
      "  \"filename\": \"./data/shopify_graphql.txt\",\n",
      "  \"query_str\": \"What fields can you retrieve from the orders object\"\n",
      "}\n",
      "Got output: \n",
      "From the orders object, you can retrieve the following fields: id, lineItems, fulfillmentOrders, and customer.\n",
      "========================\n",
      "=== Calling Function ===\n",
      "Calling function: run_graphql_query with args: {\n",
      "  \"graphql_query\": \"{ orders(first: 5, reverse: true) { edges { node { id } } } }\"\n",
      "}\n",
      "Got output: {\"data\":{\"orders\":{\"edges\":[{\"node\":{\"id\":\"gid://shopify/Order/5474475245846\"}},{\"node\":{\"id\":\"gid://shopify/Order/5474475180310\"}},{\"node\":{\"id\":\"gid://shopify/Order/5474475147542\"}},{\"node\":{\"id\":\"gid://shopify/Order/5474475082006\"}},{\"node\":{\"id\":\"gid://shopify/Order/5474475049238\"}}]}},\"extensions\":{\"cost\":{\"requestedQueryCost\":7,\"actualQueryCost\":7,\"throttleStatus\":{\"maximumAvailable\":1000.0,\"currentlyAvailable\":993,\"restoreRate\":50.0}}}}\n",
      "========================\n",
      "The most recent orders your store received are:\n",
      "\n",
      "1. Order ID: gid://shopify/Order/5474475245846\n",
      "2. Order ID: gid://shopify/Order/5474475180310\n",
      "3. Order ID: gid://shopify/Order/5474475147542\n",
      "4. Order ID: gid://shopify/Order/5474475082006\n",
      "5. Order ID: gid://shopify/Order/5474475049238\n"
     ]
    }
   ],
   "source": [
    "print(agent.chat(\"What are the most recent orders my store received\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "2ba4e809-c407-4ff2-9d93-5db2fa4342e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: graphql_writer with args: {\n",
      "  \"filename\": \"./data/shopify_graphql.txt\",\n",
      "  \"query_str\": \"What fields can you retrieve from the product object\"\n",
      "}\n",
      "Got output: \n",
      "The fields that can be retrieved from the product object are: descriptionHtml, handle, redirectNewHandle, seo, productType, standardizedProductType, productCategory, customProductType, tags, templateSuffix, giftCard, giftCardTemplateSuffix, title, vendor, collectionsToJoin, collectionsToLeave, id, metafields, options, variants, status, requiresSellingPlan, and productResourceFeedbackInput.\n",
      "========================\n",
      "=== Calling Function ===\n",
      "Calling function: run_graphql_query with args: {\n",
      "  \"graphql_query\": \"{ products(first: 10) { edges { node { id title variants { edges { node { inventoryQuantity } } } } } } }\"\n",
      "}\n",
      "Got output: {\"data\":null,\"errors\":[{\"message\":\"you must provide one of first or last\",\"locations\":[{\"line\":1,\"column\":49}],\"path\":[\"products\",\"edges\",0,\"node\",\"variants\"]}],\"extensions\":{\"cost\":{\"requestedQueryCost\":32,\"actualQueryCost\":5,\"throttleStatus\":{\"maximumAvailable\":1000.0,\"currentlyAvailable\":995,\"restoreRate\":50.0}}}}\n",
      "========================\n",
      "=== Calling Function ===\n",
      "Calling function: run_graphql_query with args: {\n",
      "  \"graphql_query\": \"{ products(first: 10) { edges { node { id title variants(first: 1) { edges { node { inventoryQuantity } } } } } } }\"\n",
      "}\n",
      "Got output: {\"data\":{\"products\":{\"edges\":[{\"node\":{\"id\":\"gid://shopify/Product/8432014819606\",\"title\":\"The Minimal Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":39}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432014852374\",\"title\":\"The Videographer Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":50}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432014917910\",\"title\":\"The Draft Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":20}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432014950678\",\"title\":\"The Complete Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":2}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432014983446\",\"title\":\"The Archived Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":50}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432015016214\",\"title\":\"The Hidden Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":50}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432015048982\",\"title\":\"The Collection Snowboard: Hydrogen\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":50}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432015081750\",\"title\":\"The Out of Stock Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":0}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432015114518\",\"title\":\"Gift Card\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":0}}]}}},{\"node\":{\"id\":\"gid://shopify/Product/8432015147286\",\"title\":\"The Inventory Not Tracked Snowboard\",\"variants\":{\"edges\":[{\"node\":{\"inventoryQuantity\":0}}]}}}]}},\"extensions\":{\"cost\":{\"requestedQueryCost\":42,\"actualQueryCost\":42,\"throttleStatus\":{\"maximumAvailable\":1000.0,\"currentlyAvailable\":958,\"restoreRate\":50.0}}}}\n",
      "========================\n",
      "Here are the products in your store and their inventory quantities:\n",
      "\n",
      "1. Product: The Minimal Snowboard\n",
      "   - Inventory Quantity: 39\n",
      "\n",
      "2. Product: The Videographer Snowboard\n",
      "   - Inventory Quantity: 50\n",
      "\n",
      "3. Product: The Draft Snowboard\n",
      "   - Inventory Quantity: 20\n",
      "\n",
      "4. Product: The Complete Snowboard\n",
      "   - Inventory Quantity: 2\n",
      "\n",
      "5. Product: The Archived Snowboard\n",
      "   - Inventory Quantity: 50\n",
      "\n",
      "6. Product: The Hidden Snowboard\n",
      "   - Inventory Quantity: 50\n",
      "\n",
      "7. Product: The Collection Snowboard: Hydrogen\n",
      "   - Inventory Quantity: 50\n",
      "\n",
      "8. Product: The Out of Stock Snowboard\n",
      "   - Inventory Quantity: 0\n",
      "\n",
      "9. Product: Gift Card\n",
      "   - Inventory Quantity: 0\n",
      "\n",
      "10. Product: The Inventory Not Tracked Snowboard\n",
      "    - Inventory Quantity: 0\n",
      "\n",
      "Please note that the inventory quantities provided are based on the variants of each product.\n"
     ]
    }
   ],
   "source": [
    "print(agent.chat(\"can you check all of the products to see if any are out of stock\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4afa63f7-fe30-4792-8b63-87a14409430f",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "We can see the Agent was able to handle the errors from the GraphQL endpoint to modify the queries, and used our documentation tool to gather more information on the schema to ulimately return a helpful response to the user. Neat!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4f1f0b6-dd8f-4a9b-bf3b-0db2cb5ca5e5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
